import requests

from typing import Union, Dict, Tuple, List

from requests.auth import HTTPBasicAuth

# https://docs.gopeed.com/site/openapi/index.html


class Gopeed(object):
    _UA_CHROME = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0)' \
                 ' AppleWebKit/535.11 (KHTML, like Gecko)' \
                 ' Chrome/17.0.963.56 Safari/535.11'

    _HEADERS = {
        "User-Agent": _UA_CHROME
    }

    def __init__(self,
                 server: str,
                 token: Union[None, str] = None,
                 username: Union[None, str] = None,
                 password: Union[None, str] = None):
        self._server = server
        self._token = token
        self._auth = HTTPBasicAuth(username, password)

        self._headers = {k: v for k, v in Gopeed._HEADERS.items()}
        if token:
            self._headers['X-Api-Token'] = token

    def _http_get(self, url: str) -> Tuple[int, Dict]:
        response = requests.get(url,
                                verify=False,
                                auth=self._auth,
                                stream=False,
                                headers=self._headers)
        return response.status_code, response.json()

    def _http_put(self, url: str) -> Tuple[int, Dict]:
        response = requests.put(url,
                                verify=False,
                                auth=self._auth,
                                stream=False,
                                headers=self._headers)
        return response.status_code, response.json()

    def _http_post(self, url: str, data: Dict) -> Tuple[int, Dict]:
        response = requests.post(url,
                                 json=data,
                                 verify=False,
                                 auth=self._auth,
                                 stream=False,
                                 headers=self._headers)
        response.json()
        return response.status_code, response.json()

    def _http_delete(self, url: str) -> Tuple[int, Dict]:
        response = requests.delete(url,
                                   verify=False,
                                   auth=self._auth,
                                   stream=False,
                                   headers=self._headers)
        return response.status_code, response.json()

    def _url(self, api: str, params: Union[None, Dict[str, str]] = None):
        if params is None or len(params) == 0:
            return f'{self._server}{api}'
        else:
            return f'{self._server}{api}?{"&".join([f"{k}={v}" for k, v in params.items()])}'

    def list_task(self,
                  status: Union[None, str] = None  # ready ┃ running ┃ pause ┃ wait ┃ error ┃ done
                  ) -> Union[None, Dict]:
        params = None
        if status:
            params = {'status': status}
        code, resp = self._http_get(url=self._url('/api/v1/tasks', params))
        return resp if code == 200 else None

    def resolve_task(self,
                     url: str,
                     username: Union[None, str] = None,
                     password: Union[None, str] = None,
                     extra_method: Union[None, str] = None,  # GET┃POST┃PUT┃DELETE┃PATCH┃HEAD┃OPTIONS┃CONNECT┃TRACE
                     extra_header: Union[None, Dict[str, str]] = None,
                     extra_body: Union[None, str] = None,
                     extra_trackers: Union[None, List[str]] = None,
                     labels: Union[None, Dict[str, str]] = None) -> Union[None, str]:
        # extra -> header
        req_extra_header = dict()
        if username:
            # set auth to header
            r = requests.PreparedRequest()
            r.headers = dict()
            HTTPBasicAuth(username, password)(r)
            req_extra_header['Authorization'] = r.headers['Authorization']
        if extra_header:
            for k, v in extra_header.items():
                req_extra_header[k] = v

        # extra
        req_extra = dict()
        if extra_method:
            req_extra['method'] = extra_method
        if len(req_extra_header) > 0:
            req_extra['header'] = req_extra_header
        if extra_body:
            req_extra['body'] = extra_body
        if extra_trackers:
            req_extra['trackers'] = extra_trackers

        # task
        task = dict()
        task['url'] = url
        if len(req_extra) > 0:
            task['extra'] = req_extra
        if labels:
            task['labels'] = labels

        # send command to server
        code, resp = self._http_post(url=self._url('/api/v1/resolve'), data=task)

        if code == 200 and resp['code'] == 0:
            return resp['data']

        return None

    def create_task(self,
                    url: Union[None, str] = None,
                    rid: Union[None, str] = None,
                    username: Union[None, str] = None,
                    password: Union[None, str] = None,
                    extra_method: Union[None, str] = None,  # GET┃POST┃PUT┃DELETE┃PATCH┃HEAD┃OPTIONS┃CONNECT┃TRACE
                    extra_header: Union[None, Dict[str, str]] = None,
                    extra_body: Union[None, str] = None,
                    extra_trackers: Union[None, List[str]] = None,
                    labels: Union[None, Dict[str, str]] = None,
                    opt_name: Union[None, str] = None,
                    opt_path: Union[None, str] = None,
                    opt_select_files: Union[None, List[int]] = None,
                    opt_extra_connections: Union[None, int] = None) -> Union[None, str]:
        if url is None and rid is None:
            print(f'ERROR: url and rid should be filled one')
            return None

        if url is not None and rid is not None:
            print(f'WARNING: both url and rid provided, url will be ignored')
            url = None

        # req -> extra -> header
        req = dict()
        if url:
            req_extra_header = dict()
            if username:
                # set auth to header
                r = requests.PreparedRequest()
                r.headers = dict()
                HTTPBasicAuth(username, password)(r)
                req_extra_header['Authorization'] = r.headers['Authorization']
            if extra_header:
                for k, v in extra_header.items():
                    req_extra_header[k] = v

            # req -> extra
            req_extra = dict()
            if extra_method:
                req_extra['method'] = extra_method
            if len(req_extra_header) > 0:
                req_extra['header'] = req_extra_header
            if extra_body:
                req_extra['body'] = extra_body
            if extra_trackers:
                req_extra['trackers'] = extra_trackers

            # req
            req['url'] = url
            if len(req_extra) > 0:
                req['extra'] = req_extra
            if labels:
                req['labels'] = labels

        # opt -> extra
        opt_extra = dict()
        if opt_extra_connections:
            opt_extra['connections'] = opt_extra_connections

        # opt
        opt = dict()
        if opt_name:
            opt['name'] = opt_name
        if opt_path:
            opt['path'] = opt_path
        if opt_select_files:
            opt['selectFiles'] = opt_select_files
        if len(opt_extra) > 0:
            opt['extra'] = opt_extra

        # task
        task = dict()
        if rid:
            task['rid'] = rid
        if len(req) > 0:
            task['req'] = req
        if len(opt) > 0:
            task['opt'] = opt

        # send command to server
        code, resp = self._http_post(url=self._url('/api/v1/tasks'), data=task)

        if code == 200 and resp['code'] == 0:
            return resp['data']

        return None

    def get_task_info(self, task_id: str) -> Union[None, Dict]:
        code, resp = self._http_get(self._url(f'/api/v1/tasks/{task_id}'))
        return resp if code == 200 else None

    def continue_task(self, task_id: str) -> bool:
        code, _ = self._http_put(self._url(f'/api/v1/tasks/{task_id}/continue'))
        return code == 200

    def continue_all_task(self) -> bool:
        code, _ = self._http_put(self._url(f'/api/v1/tasks/continue'))
        return code == 200

    def pause_task(self, task_id: str) -> bool:
        code, _ = self._http_put(self._url(f'/api/v1/tasks/{task_id}/pause'))
        return code == 200

    def pause_all_task(self) -> bool:
        code, _ = self._http_put(self._url(f'/api/v1/tasks/pause'))
        return code == 200

    def delete_task(self, task_id: str, delete_file: bool = False) -> bool:
        code, _ = self._http_delete(
            self._url(f'/api/v1/tasks/{task_id}', {'force': 'true' if delete_file else 'false'})
        )
        return code == 200

    def delete_tasks(self,
                     status: Union[None, str] = None,  # ready ┃ running ┃ pause ┃ wait ┃ error ┃ done
                     delete_file: bool = False) -> bool:
        params = {'force': 'true' if delete_file else 'false'}
        if status:
            params['status'] = status
        code, _ = self._http_delete(self._url(f'/api/v1/tasks', params))
        return code == 200
